<?php
include_once $_SERVER['DOCUMENT_ROOT'].'/lib/jObj.php';

class Jrouter extends jObj{
	private static $_instance;
	private $routes=array();
	private $inited=false;

	public function __construct($configFile=false){
	  if(!$this->init($configFile)) return $this->error('init error');
	}
	public static function inst($configFile=false){
	  if( null===self::$_instance )
	    self::$_instance=new self($configFile);
		return self::$_instance;
	}
	
	public function init($config=false){
	  if(is_array($config)){
	    if(!$this->applyConfig($config)) return false;
	  }else{
	  	if(!$this->loadConfig($config)) return false;
	  }
	  $this->inited=true;
	  return true;
	}
	/******
	 *  регистрация правила роутинга
	 *  $method get|post
	 *  $pattern - ркга урлы
	 *  $controller - строка, описывающая контроллер по CLASS/Method or FILE/CLASS/Method or File/func:<name>
	 */
	public function register($method,$pattern,$controller,$getParams=false){
	  if(empty($method) || empty($pattern) || empty($controller)) return false;
	  
	  if(!$controllerStruct=$this->parseControllerString($controller))
	    return $this->error('parse controller fail');
	  
	  $method=strtoupper($method);
	  if( !isset($this->routes[$method]) ) $this->routes[$method]=array();
	  
	  if(!empty($getParams)){
			$getParams=$this->parseGetParams($getParams);
		}
	  
	  $this->routes[$method][$pattern]=array($controllerStruct,$getParams);
	  return true;
	}
	/******
	 *  регистрация правила роутинга
	 *  $method get|post
	 *  $pattern - ркга урлы
	 *  $controller - строка, описывающая контроллер по CLASS/Method or FILE/CLASS/Method or File/func:<name>
	 */
	public function unregister($method,$pattern){
	  if(empty($method) || empty($pattern) ) return false;

	  if( !isset($this->routes[$method]) ) return $this->error('method "'.$method.'" not found');
	  if( !isset($this->routes[$method][$pattern]) ) return $this->error('roure "'.$pattern.'" not found');
	  unset($this->routes[$method][$pattern]);
	  
	  return true;
	}
	
	/***********
	 *  <className>/<methodName>
	 *  <functionName>
	 *  <path>.php/<className>/<methodName>
	 *  <path>.php/<functionName>
	 **/
	protected function parseControllerString($controller){
	  $result=array();
	  //С путем к файлу
	  if(strpos($controller, '.php/')!==false){
	    $controller=str_replace('{docRoot}', $_SERVER['DOCUMENT_ROOT'], $controller);
	    $withPath=strpos($controller, '.php/')+4;
	    $result['path']=substr($controller, 0, $withPath );
	    $controller=substr($controller, $withPath+1 );
		}

	  $t=explode('/',$controller);
	  switch( count($t) ){
	    case 2:
				$result['class']=$t[0];
				$result['method']=$t[1];
				break;
	    case 1:
				$result['function']=$t[0];
				break;
		}
		unset($t);
	  if(empty($result)) return false;
	  return $result;
	}
	
	protected function parseGetParams($str){
	  $regs=array_flip(explode('&', $str));
	  if( isset($regs['']) ) unset($regs['']);
	  $regs=array_keys($regs);
	  if(empty($regs)) return false;
	  return $regs;
	}
	
	public function setOptRules($rules){
	  if(empty($rules)) return false;
	  if( is_string($rules) ){
			$this->shareOptRules=$this->parseGetParams($rules);
	  }else $this->shareOptRules=$rules;
	  return true;
	}
	
	/*********************
	 *  Возвращает структуру для вызова контроллера:
	 *  - описание файла, класса и метода или функции
	 *  - параметры вызова
	 *******/
	public function resolve($method=false,$url=false,$data=array()){
	  if(!$this->inited) $this->init();
	  
	  if(!$method){
			$runType='h';
	    $method=strtoupper($_SERVER['REQUEST_METHOD']);
	    $url=$_SERVER['REQUEST_URI'];
		}else $runType='i';

    $t=explode('?', $url);
    $urlBase=$t[0]; $urlOptions=$t[1];
		unset($t);

    krsort($this->routes[$method]);

		$res=false;
	  foreach( $this->routes[$method] as $k=>$v){
	    $this->method=$method;
	    $this->route=&$this->routes[$method][$k];
	    
	    $rega='~'.$k.'$~u';
	    if(preg_match($rega, $urlBase, $args)){
	      $str=array_shift($args);
	      //detect Environment
	      if($runType=='h')	$env=$this->getEnvironment($method);
	      else $env=$this->getCoustomEnvironment($method, $data);
	      //get options
				
	      if( !empty($this->shareOptRules) ){
	        if(empty($v[1])) $v[1]=array();
					$v[1]=array_merge( $this->shareOptRules , $v[1]);
				}

				$opts=$this->getOptions($urlOptions,$v[1]);
				//echo $urlOptions.'<pre class="debug">'.print_r ( $v[1] ,true).'</pre>';
	      
	      
	      $res=array(
					'controller'=>$v[0],
					'arguments'=>$args,
					'options'=>$opts,
					'environment'=>$env
				);
	      break;
			}
		}
		if(empty($res)) return $this->error('Route not found');
		return $res;
	}
	
	public function run(){
	  if(!$this->inited) $this->init();
	  return $this->resolve();
	}
	
	/*********************
	 *  метод позволяющий определить свой набор окружения
	 *  $func($method, $env) получает метод и набор уже имеющегося, возвращает измененный или дополненный массив кастомного окружения
	 *****/
	private $customEnvFunc=false;
	public function setEnvironment($func){
    $this->customEnvFunc=$func;
    return true;
	}
	public function getCoustomEnvironment($method,$env){
    if($this->customEnvFunc){
			$env=call_user_func_array($this->customEnvFunc, array($method, $env));
		}
		return $env;
	}

	protected function getEnvironment($method){
	  $env=array();
	  switch($method){
	    case 'POST':
        $env['POST']=$_POST;
      	if(!empty($_FILES)) $env['FILES']=$_FILES;
	    case 'GET':
	      $env['GET']=$_GET;
				break;
	    default: return $this->error('Method "'.$method.'" not supported');
		}
		
		$env=$this->getCoustomEnvironment($method, $env);
		return $env;
  }
  
  public function loadConfig($configFile){
    if( empty($configFile) ) return $this->error('passed empty config string');
    if( !file_exists($configFile) ) return $this->error('config "'.$configFile.'" not found');
    $conf=include_once $configFile;
		if(empty($conf) ) return $this->error('config empty');
		if( !$this->applyConfig($conf) ) return false;
		return true;
	}
	
	public function applyConfig($conf){
	  if(empty($conf['routes'])) return $this->error('config routes not found');
	  if(!empty($conf['defOptRules'])) $this->setOptRules($conf['defOptRules']);
	  
    for( $i=0, $cnt=count($conf['routes']); $i<$cnt; $i++ ){
      $getParams=(!empty($conf['routes'][$i][3]))?$conf['routes'][$i][3]:false;
      $this->register( $conf['routes'][$i][0] , $conf['routes'][$i][1] ,$conf['routes'][$i][2], $getParams);
		}
		return true;
	}
	
	protected function getOptions($url,$regs){
	  $res=array();
	  $useUrlOnly=true;
	  if($this->method=='POST' || $this->method=='PUT' )$useUrlOnly=false;
		$regsVal=array();
	  for( $i=0, $cnt=count($regs); $i<$cnt; $i++ ){
	    $rega='~'.$regs[$i].'~u';
	    if( preg_match($rega, $url, $m) ){
				$res[$m[1]]=$m[2];
			}
			if( !$useUrlOnly ){
			  if(preg_match('~\((\w+)\)=\((.+)\)$~', $regs[$i], $m)){
			    //echo '<pre class="debug">'.print_r ( $m ,true).'</pre>'; exit();
			    $regsVal[$m[1]]=$m[2];
				}
			}
	  }
	  if( !$useUrlOnly ){
	    foreach( $regsVal as $k=>$v ){
	      if( !empty( $_REQUEST[$k] ) && preg_match('~^'.$v.'$~', $_REQUEST[$k], $m) ){
	        $res[$k]=$m[0];
				}
			}
			//echo '<pre class="debug">'.print_r ( $res ,true).'</pre>';exit();
	  }
	  
	  return (!empty($res))?$res:false;
	}
}
?>